
use std::{ffi::{c_char, c_int}, mem, sync::atomic::AtomicUsize};

use lazy_static::lazy_static;
use libc::{c_void, sa_family_t, size_t, sockaddr_un, socklen_t};

use super::{error::UnixError, message::linger};

pub(super) const MAX_FDS_IN_CMSG: u32 = 64;

pub(super) const SCM_RIGHTS: c_int = 0x01;

// The value Linux returns for SO_SNDBUF
// is not the size we are actually allowed to use...
// Empirically, we have to deduct 32 bytes from that.
pub(super) const RESERVED_SIZE: usize = 32;

#[cfg(target_os = "linux")]
pub(super) const SOCK_FLAGS: c_int = libc::SOCK_CLOEXEC;
#[cfg(not(target_os = "linux"))]
pub(super) const SOCK_FLAGS: c_int = 0;

#[cfg(target_os = "linux")]
pub(super) const RECVMSG_FLAGS: c_int = libc::MSG_CMSG_CLOEXEC;
#[cfg(not(target_os = "linux"))]
pub(super) const RECVMSG_FLAGS: c_int = 0;

#[cfg(target_env = "gnu")]
pub(super) type IovLen = usize;
#[cfg(target_env = "gnu")]
pub(super) type MsgControlLen = size_t;

#[cfg(not(target_env = "gnu"))]
pub(super) type IovLen = i32;
#[cfg(not(target_env = "gnu"))]
pub(super) type MsgControlLen = socklen_t;

pub(super) unsafe fn new_sockaddr_un(path: *const c_char) -> (sockaddr_un, usize) {
    let mut sockaddr: sockaddr_un = mem::zeroed();
    libc::strncpy(
        sockaddr.sun_path.as_mut_ptr(),
        path,
        sockaddr.sun_path.len() - 1,
    );
    sockaddr.sun_family = libc::AF_UNIX as sa_family_t;
    (sockaddr, mem::size_of::<sockaddr_un>())
}

lazy_static! {
    pub(super) static ref SYSTEM_SENDBUF_SIZE: usize = {
        let (tx, _) = super::ipc_bridge::channel().expect("Failed to obtain a socket for checking maximum send size");
        tx.get_system_sendbuf_size()
            .expect("Failed to obtain maximum send size for socket")
    };
}

// The pid of the current process which is used to create unique IDs
lazy_static! {
    pub(super) static ref PID: c_int = unsafe { libc::getpid() };
}

// A global count used to create unique IDs
pub(super) static SHM_COUNT: AtomicUsize = AtomicUsize::new(0);



// Make sure that the kernel doesn't return errors to readers if there's still data left after we
// close our end.
//
// See, for example, https://github.com/servo/ipc-channel/issues/29
pub(super) fn make_socket_lingering(sockfd: c_int) -> Result<(), UnixError> {
    let linger = linger {
        l_onoff: 1,
        l_linger: 30,
    };
    let err = unsafe {
        libc::setsockopt(
            sockfd,
            libc::SOL_SOCKET,
            libc::SO_LINGER,
            &linger as *const _ as *const c_void,
            mem::size_of::<linger>() as socklen_t,
        )
    };
    if err < 0 {
        return Err(UnixError::last());
    }
    Ok(())
}

